﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Http.Controllers;
using System.Web.Http.ModelBinding;
using System.Web.Http.ValueProviders;
using System.Reflection;
using System.Security.Permissions;

namespace NCS.IConnect.Core.WebApi
{
    /// <summary>
    /// The ModelBinder to bind search criteria.
    /// </summary>
    public class SearchCriteriaModelBinder : IModelBinder
    {
        #region Private Constants
        private const string PageIndexParameterName = "pageindex";
        private const string PageSizeParameterName = "pagesize";
        private const string OrderByParameterName = "orderby";
        private const string DescendingIndicator = "desc";
        #endregion

        #region Private Fields
        private static Dictionary<Type, PropertyInfo[]> writableProperties = new Dictionary<Type, PropertyInfo[]>();
        private static object syncHelper = new object();
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the default size of the page.
        /// </summary>
        /// <value>The default size of the page.</value>
        public virtual int DefaultPageSize
        {
            get { return 10; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Binds the model to a value by using the specified controller context and binding context.
        /// </summary>
        /// <param name="actionContext">The action context.</param>
        /// <param name="bindingContext">The binding context.</param>
        /// <returns>The bound value.</returns>
        [EnvironmentPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
        {
            if (!typeof(SearchCriteriaBase).IsAssignableFrom(bindingContext.ModelType))
            {
                return false;
            }

            SearchCriteriaBase criteria = (SearchCriteriaBase)Activator.CreateInstance(bindingContext.ModelType);
            IValueProvider valueProvider = bindingContext.ValueProvider;
            ValueProviderResult result = valueProvider.GetValue(PageIndexParameterName);
            int index;
            criteria.PageIndex = (null != result && int.TryParse(result.AttemptedValue, out index))? index: 1;
            result = valueProvider.GetValue(PageSizeParameterName);
            int size;
            criteria.PageSize = (null != result && int.TryParse(result.AttemptedValue, out size))? size: 10;
           
            result = valueProvider.GetValue(OrderByParameterName);
            if (null != result)
            {
                string[] split = Regex.Split(result.AttemptedValue, @"\s+");
                criteria.OrderBy = split[0];
                if (split.Length > 1 && string.Compare(split[1], DescendingIndicator, true) == 0)
                {
                    criteria.SortDirection = SortDirection.Descending;
                }
            }

            foreach (PropertyInfo property in GetWritableProperties(bindingContext.ModelType))
            {
                if (property.CanWrite && (result = valueProvider.GetValue(property.Name)) != null)
                {
                    //Ignore type imcompatible value.
                    try
                    {
                        property.SetValue(criteria, result.ConvertTo(property.PropertyType));
                    }
                    catch
                    { }
                }
            }

            bindingContext.Model = criteria;
            return true;
        }

        #endregion

        #region Private Methods
        private static PropertyInfo[] GetWritableProperties(Type type)
        {
            PropertyInfo[] properties;
            if (writableProperties.TryGetValue(type, out properties))
            {
                return properties;
            }
            lock (syncHelper)
            {
                if (writableProperties.TryGetValue(type, out properties))
                {
                    return properties;
                }
                properties = type.GetTypeInfo().DeclaredProperties.Where(p => p.CanWrite).ToArray();
                writableProperties[type] = properties;
            }
            return properties;
        }
        #endregion
    }
}
